<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    <meta name="google-site-verification" content="KEatQX-J4dYY-6J2KU_aP5X8gAJ8wS0lhylI8umX6WA" />
    <meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui">
    <link rel="shortcut icon" href="../images/favicon.ico">
    <link rel="stylesheet" href="../css/code.css" type="text/css"/>
    <link rel="stylesheet" href="../css/bootstrap.css" type="text/css"/>
    <link rel="stylesheet" href="../css/main.css" type="text/css"/>
    <title>编程小梦|Apache Kylin Cube 构建原理</title>
</head>
<body>
<nav class="navbar navbar-default navbar-static-top" style="opacity: .9" role="navigation">
    <div class="container-fluid">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle" data-toggle="collapse"
                    data-target="#bs-example-navbar-collapse-1">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">编程小梦</a>
        </div>
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav navbar-right">
                <li class="active"><a href="/">Blog</a></li>
                
                <li><a href="https://github.com/kangkaisen" target="_blank" rel="nofollow">GitHub</a></li>
                
                
                <li><a href="http://weibo.com/533234148" target="_blank" rel="nofollow">WeiBo</a></li>
                
            </ul>
        </div>
    </div>
</nav>
<div class="row" style="padding-top: 60px">
    <div class="container center-block">
        <div class="col-md-1"></div>
        <div class="col-md-10 col-sm-12">
            <h1> Apache Kylin Cube 构建原理</h1>
            <hr/>
            <p>作者: 康凯森</p>
            <p>日期: 2016-10-06</p>
            <p>分类: <a href="../tag/OLAP.html" target="_blank" >OLAP</a></p>
            <hr/>
            <!-- toc -->
<ul>
<li><a href="#apache-kylin-是什么">Apache Kylin 是什么</a></li>
<li><a href="#olap-是什么">OLAP 是什么</a></li>
<li><a href="#kylin如何实现超大数据集的秒级多维分析查询">Kylin如何实现超大数据集的秒级多维分析查询</a></li>
<li><a href="#kylin的预计算是如何实现的">Kylin的预计算是如何实现的</a></li>
<li><a href="#cube-和-cuboid是什么">Cube 和 Cuboid是什么</a></li>
<li><a href="#cuboid-的维度和指标如何转换为hbase的kv结构">Cuboid 的维度和指标如何转换为HBase的KV结构</a></li>
<li><a href="#cube-构建过程重要源码分析">Cube 构建过程重要源码分析</a></li>
<li><a href="#不同类型的指标是如何进行聚合的">不同类型的指标是如何进行聚合的</a></li>
<li><a href="#sql查询是如何转化为hbase的scan操作的">SQL查询是如何转化为HBase的Scan操作的</a></li>
<li><a href="#总结">总结</a></li>
</ul>
<!-- toc stop -->
<p>本文主要介绍了<a href="http://kylin.apache.org/">Apache Kylin</a>是如何将Hive表中的数据转化为<a href="http://blog.bcmeng.com/post/hbase-note.html">HBase</a>的KV结构，并简单介绍了Kylin的SQL查询是如何转化为HBase的Scan操作。</p>
<h3 id="apache-kylin-是什么">Apache Kylin 是什么</h3>
<p>Apache Kylin是一个开源的、基于Hadoop生态系统的OLAP查询引擎，能够通过SQL接口对十亿、甚至百亿行的超大数据集实现秒级的多维分析查询。</p>
<h3 id="olap-是什么">OLAP 是什么</h3>
<p>即联机分析处理：以复杂的分析型查询为主，需要扫描，聚合大量数据。</p>
<h3 id="kylin如何实现超大数据集的秒级多维分析查询">Kylin如何实现超大数据集的秒级多维分析查询</h3>
<p><code>预计算</code></p>
<p>对于超大数据集的复杂查询，既然现场计算需要花费较长时间，那么根据空间换时间的原理，我们就可以提前将所有可能的计算结果计算并存储下来，从而实现超大数据集的秒级多维分析查询。</p>
<h3 id="kylin的预计算是如何实现的">Kylin的预计算是如何实现的</h3>
<p>将数据源Hive表中的数据按照指定的维度和指标 由计算引擎MapReduce离线计算出所有可能的查询结果(即Cube)存储到HBase中。</p>
<h3 id="cube-和-cuboid是什么">Cube 和 Cuboid是什么</h3>
<p>简单地说，一个cube就是一个Hive表的数据按照指定维度与指标计算出的所有组合结果。</p>
<p>其中每一种维度组合称为cuboid，一个cuboid包含一种具体维度组合下所有指标的值。</p>
<p>如下图，整个立方体称为1个cube，立方体中每个网格点称为1个cuboid，图中（A,B,C,D）和（A，D）都是cuboid，特别的，（A,B,C,D）称为Base cuboid。cube的计算过程是逐层计算的，首先计算Base cuboid，然后计算维度数依次减少，逐层向下计算每层的cuboid。</p>
<p><img src="http://static.zybuluo.com/kangkaisen/1em2eqhk4j8vkhb3ggjmz5qn/cuboid.png" alt="cuboid.png-198.5kB">图1</p>
<h3 id="cuboid-的维度和指标如何转换为hbase的kv结构">Cuboid 的维度和指标如何转换为HBase的KV结构</h3>
<p>简单的说Cuboid的维度会映射为HBase的Rowkey，Cuboid的指标会映射为HBase的Value。如下图所示：
<img src="http://static.zybuluo.com/kangkaisen/gpv1v5kqmjtfk9v2k226bfay/cube-kv.png" alt="cube-kv.png-356.2kB">图2</p>
<p>如上图原始表所示：Hive表有两个维度列<code>year</code>和<code>city</code>，有一个指标列<code>price</code>。</p>
<p>如上图预聚合表所示：我们具体要计算的是<code>year</code>和<code>city</code>这两个维度所有维度组合（即4个cuboid）下的<code>sum(priece)</code>指标，这个指标的具体计算过程就是由MapReduce完成的。</p>
<p>如上图字典编码所示：为了节省存储资源，Kylin对维度值进行了字典编码。图中将<code>beijing</code>和<code>shanghai</code>依次编码为0和1。</p>
<p>如上图HBase KV存储所示：在计算cuboid过程中，会将Hive表的数据转化为HBase的KV形式。Rowkey的具体格式是<code>cuboid id + 具体的维度值</code>（最新的Rowkey中为了并发查询还加入了ShardKey），以预聚合表内容的第2行为例，其维度组合是（year，city），所以cuboid id就是00000011，cuboid是8位，具体维度值是1994和shanghai，所以编码后的维度值对应上图的字典编码也是11，所以HBase的Rowkey就是0000001111，对应的HBase Value就是<code>sum(priece)</code>的具体值。</p>
<p>所有的cuboid计算完成后，会将cuboid转化为HBase的<code>KeyValue</code>格式生成HBase的HFile，最后将HFile load进cube对应的HBase表中。</p>
<h3 id="cube-构建过程重要源码分析">Cube 构建过程重要源码分析</h3>
<h4 id="1-从hive表生成base-cuboid">1 从Hive表生成Base Cuboid</h4>
<p>在实际的cube构建过程中，会首先根据cube的Hive事实表和维表生成一张大宽表，然后计算大宽表列的基数，建立维度字典，估算cuboid的大小，建立cube对应的HBase表，再计算base cuboid。</p>
<p><strong>计算base cuboid就是一个MapReduce作业，其输入是上面提到的Hive大宽表，输出是的key是各种维度组合，value是Hive大宽表中指标的值</strong>。</p>
<pre><code>org.apache.kylin.engine.mr.steps.BaseCuboidJob

map 阶段生成key-value的代码如下：

    protected void outputKV(Context context) throws IOException, InterruptedException {
        intermediateTableDesc.sanityCheck(bytesSplitter);

        byte[] rowKey = buildKey(bytesSplitter.getSplitBuffers());
        outputKey.set(rowKey, 0, rowKey.length);

        ByteBuffer valueBuf = buildValue(bytesSplitter.getSplitBuffers());
        outputValue.set(valueBuf.array(), 0, valueBuf.position());
        context.write(outputKey, outputValue);
    }

所有计算cuboid的reduce阶段代码都一样，见下面。
</code></pre><h4 id="2-从base-cuboid-逐层计算-cuboid。">2 从Base Cuboid 逐层计算 Cuboid。</h4>
<p>从base cuboid 逐层计算每层的cuboid，也是MapReduce作业，map阶段每层维度数依次减少，reduce阶段对指标进行聚合。</p>
<pre><code>org.apache.kylin.engine.mr.steps.CuboidReducer

    public void reduce(Text key, Iterable&lt;Text&gt; values, Context context) throws IOException, InterruptedException {
        aggs.reset();  //MeasureAggregators 根据每种指标的不同类型对指标进行聚合

        for (Text value : values) {
            codec.decode(ByteBuffer.wrap(value.getBytes(), 0, value.getLength()), input);
            if (cuboidLevel &gt; 0) { // Base Cuboid 的 cuboidLevel 是0
                aggs.aggregate(input, needAggr); //指标进行进一步聚合
            } else {
                aggs.aggregate(input);
            }
        }
        aggs.collectStates(result);

        ByteBuffer valueBuf = codec.encode(result);

        outputValue.set(valueBuf.array(), 0, valueBuf.position());
        context.write(key, outputValue);
</code></pre><h4 id="3-cuboid-转化为hbase的hfile。">3 Cuboid 转化为HBase的HFile。</h4>
<p>主要就是数据格式的转化。详情请参考：
<a href="http://blog.bcmeng.com/post/hbase-bulkload.html">Hive 数据 bulkload 导入 HBase</a></p>
<h3 id="不同类型的指标是如何进行聚合的">不同类型的指标是如何进行聚合的</h3>
<p>每种不同的指标都会有对应的聚合算法，所有指标聚合的基类是<code>org.apache.kylin.measure.MeasureAggregator</code>。其核心方法如下：</p>
<pre><code>    abstract public void reset();
    //不同类型的指标算法会实现该方法
    abstract public void aggregate(V value);

    abstract public V getState();
</code></pre><p>以最简单的long类型的sum指标为例：</p>
<pre><code>public class LongSumAggregator extends MeasureAggregator&lt;LongMutable&gt; {

    LongMutable sum = new LongMutable();

    @Override
    public void reset() {
        sum.set(0);
    }

    @Override
    public void aggregate(LongMutable value) {
        sum.set(sum.get() + value.get());
    }

    @Override
    public LongMutable getState() {
        return sum;
    }
}
</code></pre><h3 id="sql查询是如何转化为hbase的scan操作的">SQL查询是如何转化为HBase的Scan操作的</h3>
<p>还是以图2举例，假设查询SQL如下：</p>
<pre><code>select year, sum(price)
from table
where city = &quot;beijing&quot;
group by year
</code></pre><p>这个SQL涉及维度<code>year</code>和<code>city</code>，所以其对应的cuboid是00000011，又因为city的值是确定的<code>beijing</code>,所以在Scan HBase时就会Scan Rowkey以00000011开头且city的值是<code>beijing</code>的行，取到对应指标<code>sum(price)</code>的值，返回给用户。</p>
<h3 id="总结">总结</h3>
<p>本文主要介绍了Apache Kylin是如何将Hive表中的数据转化为HBase的KV结构，并简单介绍了Kylin的SQL查询是如何转化为HBase的Scan操作。希望对大家有所帮助。</p>

            <hr/>
            <div style="padding: 0; margin: 10px auto; width: 90%; text-align: center">
                <button id="rewardButton" , disable="enable" ,
                        onclick="var qr = document.getElementById('QR'); if (qr.style.display === 'none') {qr.style.display='block';} else {qr.style.display='none'}"
                        ,
                        style="cursor: pointer; border: 0; outline: 0; border-radius: 100%; padding: 0; margin: 0; letter-spacing: normal; text-transform: none; text-indent: 0px; text-shadow: none">
                    <span style="display: inline-block; width: 60px; height: 60px; border-radius: 100%; line-height: 58px; color: #fff; font-size:36px; font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, Helvetica, STKaiti, SimSun, serif; background: rgb(236,96,0)">赞</span>
                </button>
                <div id="QR" style="display: none;">
                    <p><img src="../images/weixin.jpeg" width="200" /></p>
                    <p><img src="../images/zhifubao.jpeg" width="200" /></p>
                </div>

            </div>
            <h3>评论</h3>
            <div id="vcomment"></div>
        </div>
        <div class="col-md-1"></div>
    </div>
</div>

<div class="row" style="padding-top: 60px">
    <div class="container center-block">
        <div class="col-md-1"></div>
        <div class="col-md-10 col-sm-12">
            <div class="ds-thread"
                 data-thread-key=5871f5bcd2f092c392ca4d57
                 data-title=Apache Kylin Cube 构建原理
                 data-url=kylin-cube>
            </div>
        </div>
        <div class="col-md-1"></div>
    </div>
</div>

<div class="footer">
    <a href="https://www.bcmeng.com/" target="_blank"  rel="nofollow">康凯森</a>
</div>

<script src="../js/code.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script src="../js/jquery.min.js"></script>
<script src="../js/bootstrap.js"></script>
<script>
    var _hmt = _hmt || [];
    (function() {
        var hm = document.createElement("script");
        hm.src = "https://hm.baidu.com/hm.js?1d198a377ef466190881d1c021155925";
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(hm, s);
    })();
</script>
<script src="../js/av-min.js"></script>
<script src='../js/Valine.min.js'></script>
<script type="text/javascript">
    window.valine = new Valine({
        el: '#vcomment' ,
        verify: true,
        notify: true,
        appId: 'BlLnB0re5OzQVzrgEplAxkyg-gzGzoHsz',
        appKey: 'wUyxSV0U4Vi7oK1EHK6ipErv',
        placeholder: '欢迎评论'
    });
</script>

</body>
</html>